Skip to content

Tiptap RTE: Adds support for Reusable Content of Blocks - #23195

Merged
madsrasmussen merged 23 commits into
v19/devfrom
v19/feature/rcob-rte-blocks
Sep 23, 2026
Merged

madsrasmussen merged 23 commits into
v19/devfrom
v19/feature/rcob-rte-blocks

Conversation

@leekelleher

@leekelleher leekelleher commented Jun 24, 2026 •

Copy link
Copy Markdown
Member

Description

Wires up the Tiptap RTE block extension to work with Element Library (reusable content) references, following the general Reusable Content of Blocks feature that landed in #22448.

Previously, inserting a Library Element into an RTE would silently do nothing - Library Elements only have a layout entry (no local contentData), so the block extension never detected them.

RTE blocks are now identified by their own layout key rather than by content key. That's what makes Library Elements work at all: the same reusable element can now be inserted multiple times in one RTE, each instance tracked and deletable/undoable independently, instead of every instance colliding on the one content key they all share. Existing markup without the new data-key attribute keeps working unchanged.

While in there, also fixed a bug where deleting a mix of a local block and a Library Element block in the same edit could leave an orphaned, no-longer-backed block element behind in the markup.

This PR only covers the RTE/Tiptap side. A few pieces that came up alongside it moved to their own PRs for separate review: the server-side markup parsing changes (#23870), a shared block-entry refactor unrelated to RTE specifically (#23871), and the RTE block entry's name label/reference styling (#23872).

Breaking changes

  • The Tiptap editor.commands.setBlock() and setBlockInline() commands now require a layoutKey alongside contentKey.
  • UmbBlockRteManagerContext/UmbBlockRteEntriesContext's deletion-related methods (requestPendingDeletion, clearPendingDeletion, removeOneLayout, removeManyLayouts, delete) now expect a layout key rather than a content key - consistent with the same change Global Elements: Reusable Content of Blocks #22448 already made for Block Grid/List/Single.
  • UmbPropertyEditorUiRteElementBase._filterUnusedBlocks() is deprecated in favour of _filterUnusedBlocksFromMarkup(), which derives the used layout keys itself (including the legacy fallback). Scheduled for removal in Umbraco 21.

How to test

  1. Create a Document Type with a Rich Text property, configured to allow at least one Block Type.
  2. Set up an Element Library with at least one published element.
  3. Open a content node, go into the RTE, and insert the same Library Element twice - both instances should appear independently in the editor.
  4. Delete one instance - only that one should disappear; the other stays.
  5. Undo - the deleted instance comes back.
  6. Load an existing RTE document that already has local (non-reference) blocks - they should still render correctly, no regression.

For the reference-block frame styling and unpublished-state fix, see #23872 and #23871 - this PR alone won't show those visually.

- Add `allContents` observable to `UmbBlockManagerContext` merging local + external content
- Switch block observation from `context.contents` to `combineLatest([layouts, allContents])`; skips blocks whose content type hasn't resolved yet (external content arrives async)
- Add `data-key` attribute (layout key) to RTE block ProseMirror nodes so duplicate library element instances are tracked independently; `data-content-key` is retained for backwards-compat
- Legacy markup (no `data-key`) defaults `data-key = data-content-key` on parse, which is safe because pre-v19 data always has `layout.key === layout.contentKey`
- Switch `_filterUnusedBlocks` and undo stash from content-key to layout-key tracking; external blocks skip local content stash (their content lives in `#externalContentValues`)
- Rename `contentKey` params to `layoutKey` in `requestPendingDeletion`, `clearPendingDeletion`, `removeOneLayout`, `removeManyLayouts`, and `delete` — semantically safe for legacy data
- `umb-rte-block` exposes `data-key` as the primary property and deprecates `data-content-key` / `contentKey` (removal v21)
Copilot AI review requested due to automatic review settings June 24, 2026 14:15
@claude

This comment was marked as outdated.

@leekelleher leekelleher changed the title Tiptap RTE Blocks: support Element Library (reusable content) references Tiptap RTE: Adds support for Reusable Content of Blocks Jun 24, 2026

This comment was marked as outdated.

@claude claude Bot added category/ux User experience category/ui User interface labels Jun 24, 2026
- Remove accidental margin-top regression on all RTE block entries
- Fix settings not stashed/restored for external blocks on delete+undo
- Reduce deep nesting in #restoreUnusedBlocks / #setUnusedBlockLookups
- De-duplicate getAttrs and insert-attrs logic in block.tiptap-extension.ts
- Clarify JSDoc on allContents and _filterUnusedBlocks parameter semantics
The umb-entity-frame label tab clips when a block is at the top of the
RTE without this spacing.
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts
Reduce cognitive complexity of #restoreUnusedBlocks by extracting
content/settings restoration into helper methods, use .includes()/Set.has()
over indexOf()/Array.includes(), and use RegExp.exec() over String.match().
@sonarqubecloud

This comment was marked as outdated.

Adds a sibling test to the incoming v19/dev doc-walk test, asserting
the walk also finds layout keys (including the legacy data-key-missing
fallback), matching how #updateBlocks now discovers blocks post-merge.
Block RTE, Grid, List and Single entry elements each duplicated an
identical #updateExposedState() method and its three backing
observers. Move the derivation onto UmbBlockEntryContext as a single
mergeObservables-based isExposed observable, consumed the same way by
all four elements. Fixes a SonarQube new-code duplication gate failure
on block-rte-entry.element.ts.
These two cases were merged up from v17/dev before RTE blocks gained a
data-key layout attribute alongside data-content-key, so their
expected markup no longer matches the block node's actual (correct)
output. Use distinct data-key/data-content-key values to assert the
div-nesting behaviour without relying on the legacy same-value
back-fill, which is already covered by block.tiptap-extension.test.ts.
BlockRegex required data-content-key to appear immediately after the
tag name (or an optional class attribute), so it never matched once
data-key started being emitted before data-content-key. That left
RTE blocks rendering as unreplaced placeholders on the front end
after being re-saved through the editor. Widen the pattern to accept
attributes on either side of data-content-key.
@leekelleher
leekelleher marked this pull request as draft September 7, 2026 12:08
to update when UFM label has resolved.
…work to their own PRs

Server-side data-key parsing/stripping, the shared isExposed
derivation on UmbBlockEntryContext, and the RTE entry umb-entity-frame
UI are independent of the layout-key rework and are easier reviewed
separately. Moved to:

- v19/bugfix/rte-block-data-key-parsing
- v19/improvement/block-entry-is-exposed
- v19/feature/rcob-rte-block-entry-ui

This branch now covers only identifying RTE blocks by layout key.
…dering test

The three existing assertions read element.value, which only re-syncs
from manager state via a property-context-dependent observer that this
fixture (no real UMB_PROPERTY_CONTEXT provider) never wires up, so
they silently asserted on stale data regardless of the code under
test. Assert against the manager context directly instead — that's
the actual contract _filterUnusedBlocksByLayoutKeys owns.

Also adds a test pinning the layout-before-content/settings removal
order, which nothing previously guarded: reverting the reorder in
rte-base.element.ts turned none of the existing tests red.
@leekelleher
leekelleher marked this pull request as ready for review September 7, 2026 16:00
@claude

This comment was marked as resolved.

@leekelleher

Copy link
Copy Markdown
Member Author

Applied the regex-hoisting suggestion in c5ce29f - layoutKeyRegex/contentKeyRegex are now compiled once above the loop in #getUsedRteBlockLayoutKeysInMarkup instead of per-iteration.

@sonarqubecloud

sonarqubecloud Bot commented Sep 8, 2026

Copy link
Copy Markdown

@madsrasmussen
madsrasmussen merged commit c611091 into v19/dev Sep 23, 2026
35 checks passed
@madsrasmussen
madsrasmussen deleted the v19/feature/rcob-rte-blocks branch September 23, 2026 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inserting a Library element into a Rich Text Editor block does not add the block to the editor

4 participants